﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;

namespace Firefly.Framework.ContentManagement
{
    public partial class ContentTracker
    {
        internal class LoadAsyncParams
        {
            internal Type AssetType { get; set; }
            internal AssetTracker Tracker { get; set; }
            internal List<AssetLoaded> ItemLoadedMethods { get; set; }

            internal LoadAsyncParams(Type type, string name, AssetLoaded loadedMethod)
            {
                ItemLoadedMethods = new List<AssetLoaded>();
                AssetType = type;
                ItemLoadedMethods.Add(loadedMethod);
                Tracker = new AssetTracker();
                Tracker.AssetName = name;
                Tracker.RefCount = 1;
                Tracker.Status = AssetStatus.Loading;
            }
        }

        private Queue<LoadAsyncParams> _loadItemsQueue;
        private Thread _loadThread;
        private AutoResetEvent _loadResetEvent;

        public AssetTracker LoadAsync<T>(string assetName, AssetLoaded itemLoadedMethod)
        {
            AssetTracker tracker = null;

            if (_loadedAssets.ContainsKey(assetName))
            {
                tracker = _loadedAssets[assetName];
                tracker.RefCount++;

                if (itemLoadedMethod != null)
                {
                    itemLoadedMethod(tracker.Asset);
                }
            }
            else
            {
                if (_loadThread == null)
                {
                    _loadThread = new Thread(new ThreadStart(LoadingThreadWorker));
                    _loadItemsQueue = new Queue<LoadAsyncParams>();
                    _loadResetEvent = new AutoResetEvent(false);
                    _loadThread.Start();
                }

                lock (_loadItemsQueue)
                {
                    Queue<LoadAsyncParams>.Enumerator enumer = _loadItemsQueue.GetEnumerator();

                    while (enumer.MoveNext())
                    {
                        if (enumer.Current.Tracker.AssetName == assetName)
                        {
                            enumer.Current.ItemLoadedMethods.Add(itemLoadedMethod);
                            tracker = enumer.Current.Tracker;
                            tracker.RefCount++;
                            break;
                        }
                    }

                    if (tracker == null)
                    {
                        LoadAsyncParams args = new LoadAsyncParams(typeof(T), assetName, itemLoadedMethod);
                        tracker = args.Tracker;
                        _loadItemsQueue.Enqueue(args);
                    }
                }

                _loadResetEvent.Set();
            }

            return tracker;
        }

        private void LoadingThreadWorker()
        {
            LoadAsyncParams args;

            while (true)
            {
                while (_loadItemsQueue.Count > 0 && !IsLoading)
                {
                    lock (_loadItemsQueue)
                    {
                        args = _loadItemsQueue.Peek();
                    }

                    CallGenericLoad(args);

                    if (_loadedAssets.ContainsKey(args.Tracker.AssetName))
                    {
                        foreach (AssetLoaded method in args.ItemLoadedMethods)
                        {
                            if (method != null)
                            {
                                method.Invoke(args.Tracker.Asset);
                            }
                        }

                        args.Tracker.Status = AssetStatus.Active;
                    }

                    lock (_loadItemsQueue)
                    {
                        _loadItemsQueue.Dequeue();
                    }
                }

                _loadResetEvent.WaitOne();
            }
        }

        private void CallGenericLoad(LoadAsyncParams loadArgs)
        {
            MethodInfo genericMethod =
                typeof(ContentTracker).GetMethod(
                    "Load",
                    BindingFlags.NonPublic | BindingFlags.Instance,
                    null,
                    new Type[] { typeof(string), typeof(AssetTracker) },
                    null);

            if (loadArgs.AssetType == null)
            {
                return;
            }

            MethodInfo closedMethod = genericMethod.MakeGenericMethod(loadArgs.AssetType);
            closedMethod.Invoke(
                this,
                new object[] { 
                    loadArgs.Tracker.AssetName, 
                    loadArgs.Tracker });
        }
    }
}
